﻿using System;
using System.Text;

namespace ICommonLibrary {

    public class ArrayUtil {

        /// <summary>
        /// Create a copy of the given array. 
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static Array Clone(Array array) {
	        Type type = array.GetType().GetElementType();
		    Array clone =  Array.CreateInstance(type, array.Length);
            Array.Copy(array, 0, clone, 0, array.Length);
		    return clone;
	    }

        /// <summary>
        /// Compares two arrays of comparable objects. Allowing for null.
        /// </summary>
        /// <param name="array1"></param>
        /// <param name="array2"></param>
        /// <returns>a negative integer, zero, or a positive integer as the first 
        /// array is less than, equal to, or greater than the second.</returns>
	    public static int Compares(IComparable[] array1, IComparable[] array2) {
		    if (array1 == null && array2 == null)
			    return 0;
		    if (array1 == null)
			    return -1;
		    if (array2 == null)
			    return 1;
		    int l = array1.Length;
		    int r = array2.Length;
		    if (l != r)
			    return l - r;
		    for (int i = 0; i < l; i++) {
			    int compareTo = Util.Compares(array1[i], array2[i]);
			    if (compareTo != 0)
				    return compareTo;
		    }
		    return 0;
	    }

        public new static bool Equals(object o1, object o2) {
            throw new InvalidOperationException("You may want to call Equals(Array, Array)");
        }

        /// <summary>
        /// Compares two arrays.
        /// </summary>
        /// <param name="array1"></param>
        /// <param name="array2"></param>
        /// <returns>true if the two arrays have same content, false otherwise.</returns>
        public static bool Equals(Array array1, Array array2) {
    		
		    if (array1 == null)
			    return array2 == null;
    		
		    if (array2 == null)	
			    return false;
    		
		    int leftLength  = array1.Length;
		    int rightLength = array2.Length;
    		
		    if (leftLength != rightLength)
			    return false;
    		
		    for (int i = 0; i < leftLength; i++) {
			    if (!Util.Equals(array1.GetValue(i), array2.GetValue(i)))
				    return false;
		    }
		    return true;
    		
	    }

        /// <summary>
        /// Extract a sub-array from the given array. Out-of-bound indices are
        /// safely ignored.
        /// </summary>
        /// <param name="array"></param>
        /// <param name="indicesOfExtraction"></param>
        /// <returns>A new array</returns>
        public static Array Extract(Array array, int[] indicesOfExtraction) {
    		
		    Asserts.NotNull(array);
		    Asserts.NotNull(indicesOfExtraction);

            Array c = Clone(array);
		    int length = c.Length;
    		
		    int size = 0;		
		    for (int i = 0; i < indicesOfExtraction.Length; i++) {
			    int next = indicesOfExtraction[i];
			    if (next >= 0 && next < length) {
			        c.SetValue(array.GetValue(next), size);
			        size++;
			    }
		    }
		    return Trunc(c, size);
	    }

        /// <summary>
        /// Extract not-null elements from the given array.
        /// </summary>
        /// <param name="array"></param>
        /// <returns>A new array which contains all not-null elements 
        /// of the supplied array</returns>
        public static Array ExtractNotNull(Array array) {
		    Asserts.NotNull(array, "array is null");
    	
		    Array b = Clone(array);		
		    int size = 0;
		    for (int i = 0; i < array.Length; i++) {
			    if (array.GetValue(i) != null) {
				    b.SetValue(array.GetValue(i), size);
				    size++;
			    }
		    }
		    return Trunc(b, size);
	    }

        /// <summary>
        /// Searches for the first occurence of an element from the supplied
        /// array.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="element"></param>
        /// <returns>the index of the element, or returns -1 if the element was not
        /// found.</returns>
        public static int IndexOf(Array a, object element) {
            Asserts.NotNull(a);

	        for (int i = 0; i < a.Length; i++) {
                if (Util.Equals(element, a.GetValue(i)))
				    return i;
		    }
	        return -1;
	    }

        /// <summary>
        /// Searches for the first occurence of a string from the supplied 
        /// string array.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="s"></param>
        /// <returns>the index of the element, or returns -1 if the string was not
        /// found .</returns>
	    public static int IndexOfIgnoreCase(string[] a, string s) {
		    return IndexOfIgnoreCase(a, s, 0);
	    }

        /// <summary>
        /// Searches for the first occurence of a string from the supplied 
        /// string array.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="s"></param>
        /// <param name="from"></param>
        /// <returns>the index of the element, or returns -1 if the string was not
        /// found .</returns>
	    public static int IndexOfIgnoreCase(string[] a, string s, int from) {
            Asserts.NotNull(a);
            Asserts.True(from >= 0, "from is negative.");

	        for (int i = from; i < a.Length; i++) {
			    if (Util.EqualsIgnoreCase(s, a[i]))
				    return i;
		    }
		    return -1;
	    }

        /// <summary>
        /// Returns true if the given array is not null and has more than
        /// one element.
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static bool IsEmpty(Array array) {
            return Size(array) == 0;
        }

        /// <summary>
        /// Build a string by concatnating every element of an array with
        /// the specified string as glue.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="glue"></param>
        /// <param name="a"></param>
        /// <param name="ignoreNullOrEmpty"></param>
        /// <returns></returns>
	    public static string Join<T>(string glue, T[] a, bool ignoreNullOrEmpty) {
		    if (a == null)
			    throw new NullReferenceException();

            StringBuilder buf = new StringBuilder(100);

	        bool first = true;
            foreach (var elem in a) {
                string current = Util.GetString(elem);
                if (string.IsNullOrEmpty(current) && ignoreNullOrEmpty) {
                    continue;
                }

                if (!first && glue != null) {
                    buf.Append(glue);
                }
                buf.Append(current);
                first = false;
            }
		    return buf.ToString();
	    }

        /// <summary>
        /// Merge two arrays of the Same type. An empty array is returned 
        /// if both arrays are null.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="array1"></param>
        /// <param name="array2"></param>
        /// <returns></returns>
	    public static T[] Merge<T>(T[] array1, T[] array2) {
    		
		    if (array1 == null && array2 == null)
			    return new T[0];
    		
		    if (array1 == null)
			    return array2;
    		
		    if (array2 == null)
			    return array1;
    		
		    if (array1.GetType().GetElementType() != array2.GetType().GetElementType())
			    throw new Exception("Only two arrays of the Same type can be merged");
    		
		    T[] merged = (T[]) Array.CreateInstance(array1.GetType().GetElementType(), 
                                                    array1.Length + array2.Length);
            Array.Copy(array1, 0, merged, 0, array1.Length);
            Array.Copy(array2, 0, merged, array1.Length, array2.Length);
		    return merged;
	    }

        /// <summary>
        /// Returna the size of the given array. 0 is returned if the given
        /// array is null.
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static int Size(Array array) {
            return array == null ? 0 : array.Length;
        }

        /// <summary>
        /// Extract a subarray.
        /// </summary>
        /// <param name="array"></param>
        /// <param name="beginIndex"></param>
        /// <returns></returns>
        public static object Subarray(Array array, int beginIndex) {
		    if (array == null) 
			    return null;
    		
		    return Subarray(array, beginIndex, array.Length);
	    }
        
        /// <summary>
        /// Extract a subarray.
        /// </summary>
        /// <param name="array"></param>
        /// <param name="beginIndex"></param>
        /// <param name="endIndex"></param>
        /// <returns></returns>
        public static object Subarray(Array array, int beginIndex, int endIndex) {
		    if (array == null) 
			    return null;
    		
		    Type type = array.GetType().GetElementType();		
		    int length = array.Length;
    		
		    // check begin index
		    beginIndex = Math.Max(beginIndex, 0);
		    if (beginIndex > length || endIndex < beginIndex)
			    return Array.CreateInstance(type, 0);
    		
		    // check end index
		    if (endIndex > length) {
			    endIndex = length;
		    }

            Array subarray = Array.CreateInstance(type, endIndex - beginIndex);
    	
		    for (int i = beginIndex; i < endIndex; i++) {
		        subarray.SetValue(array.GetValue(i), i - beginIndex);
		    }

	        return subarray;
	    }

        /// <summary>
        /// Converts all elements of the given array to strings. Null 
        /// elements remain null in the result array.
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static string[] ToStringArray(Array array) {
		    Asserts.NotNull(array);

            int length = array.Length;		
		    string[] s = new string[length];
		    for (int i = 0; i < length; i++) {
                s[i] = Util.GetString(array.GetValue(i), null);
		    }
		    return s;
	    }

        /// <summary>
        /// Returns a new array containing the leading 
        /// 
        ///     Math.min(maxSize, array.Length) 
        /// 
        /// elements of the supplied array.
        /// </summary>
        /// <param name="array"></param>
        /// <param name="maxSize"></param>
        /// <returns></returns>
        public static Array Trunc(Array array, int maxSize) {
		    Asserts.NotNull(array);
		    Asserts.True(maxSize >= 0);
    		
		    int size = array.Length;
		    if (size <= maxSize)
			    return Clone(array);
    		
		    Array trunc = Array.CreateInstance(array.GetType().GetElementType(), maxSize);
            Array.Copy(array, 0, trunc, 0, maxSize);
		    return trunc;
	    }

    }
}
